package com.ceair.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ceair.constant.Flowable7Constants;
import com.ceair.entity.BpmnDefinitions;
import com.ceair.entity.ProcessDefinition;
import com.ceair.entity.request.*;
import com.ceair.entity.vo.ProcessDefinitionVO;
import com.ceair.enums.ProcessStatus;
import com.ceair.enums.ResultCode;
import com.ceair.exception.BusinessException;
import com.ceair.mapper.ProcessDefinitionMapper;
import com.ceair.mongodb.BpmnDefinitionRepository;
import com.ceair.service.IProcessDefinitionService;
import com.ceair.utils.ProcessDefinitionStructMapper;
import io.micrometer.common.util.StringUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RepositoryService;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * <p>
 * 流程定义表 服务实现类
 * </p>
 *
 * @author wangbaohai
 * @since 2025-04-12
 */
@Slf4j
@Service
@RequiredArgsConstructor
public class ProcessDefinitionServiceImpl extends ServiceImpl<ProcessDefinitionMapper, ProcessDefinition> implements IProcessDefinitionService {

    private final BpmnDefinitionRepository bpmnDefinitionRepository;
    private final RepositoryService repositoryService;

    /**
     * 保存或更新流程定义信息。
     *
     * @param processDefinitionInOrUpReq 流程定义的输入参数对象，包含流程键（processKey）、流程版本（processVersion）等信息。
     *                                   该参数不能为空，且关键字段（如 processKey 和 processVersion）也不能为空。
     * @return 返回保存或更新后的流程定义视图对象（ProcessDefinitionVO）。
     * @throws IllegalArgumentException 如果输入参数为空或关键字段缺失，则抛出此异常。
     * @throws BusinessException        如果存在重复的流程定义（唯一键冲突）或其他业务逻辑错误，则抛出此异常。
     * @throws Exception                如果在保存或更新过程中发生系统级错误，则抛出此异常。
     */
    @Override
    public ProcessDefinitionVO save(ProcessDefinitionInOrUpReq processDefinitionInOrUpReq) {
        try {
            // 参数校验：确保输入参数不为空且关键字段完整
            if (processDefinitionInOrUpReq == null || StringUtils.isEmpty(processDefinitionInOrUpReq.getProcessKey()) || Objects.isNull(processDefinitionInOrUpReq.getProcessVersion())) {
                log.error("ProcessDefinitionInOrUpReq 参数为空或关键字段缺失, 输入参数: {}", processDefinitionInOrUpReq);
                throw new IllegalArgumentException("ProcessDefinitionInOrUpReq 参数或者关键字段不能为空");
            }

            // 初始化 ProcessDefinition 对象，用于存储流程定义信息
            ProcessDefinition processDefinition = new ProcessDefinition();

            // 将输入参数中的非空数据赋值给 ProcessDefinition 对象，避免空值覆盖已有数据
            BeanUtil.copyProperties(processDefinitionInOrUpReq, processDefinition,
                    CopyOptions.create().ignoreNullValue());

            // 如果 id 为空，说明是新增流程操作，需要进行唯一索引校验（processKey + processVersion）
            if (processDefinition.getId() == null) {
                // 根据 processKey 和 processVersion 查询是否存在相同记录
                List<ProcessDefinition> existProcessDefinitions = lambdaQuery().eq(ProcessDefinition::getProcessKey,
                        processDefinition.getProcessKey()).eq(ProcessDefinition::getProcessVersion,
                        processDefinition.getProcessVersion()).list();

                // 如果存在相同记录，抛出业务异常
                if (!existProcessDefinitions.isEmpty()) {
                    log.error("存在相同流程定义，processKey:{}, processVersion:{}", processDefinition.getProcessKey(),
                            processDefinition.getProcessVersion());
                    throw new BusinessException(ResultCode.FAILED.getCode(),
                            "存在相同流程定义，重复唯一键是：" + processDefinition.getProcessKey() + "-" + processDefinition.getProcessVersion());
                }
            }

            // 保存或更新流程定义信息
            saveOrUpdate(processDefinition);

            // 将保存后的 ProcessDefinition 对象转换为视图对象并返回
            return ProcessDefinitionStructMapper.INSTANCE.toVO(processDefinition);
        } catch (IllegalArgumentException e) {
            // 参数校验异常处理
            log.error("参数校验失败, 输入参数: {}, 错误信息: {}", processDefinitionInOrUpReq, e.getMessage(), e);
            throw e;
        } catch (BusinessException e) {
            // 业务异常处理
            log.error("业务异常, 输入参数: {}, 错误信息: {}", processDefinitionInOrUpReq, e.getMessage(), e);
            throw e;
        } catch (Exception e) {
            // 系统异常处理
            log.error("保存或更新流程定义时发生系统错误, 输入参数: {}", processDefinitionInOrUpReq, e);
            throw new BusinessException("保存或更新流程定义失败", e);
        }
    }

    /**
     * 分页查询流程定义信息
     * <p>
     * 本方法根据用户请求参数，分页查询流程定义信息，并处理分页逻辑和异常情况
     *
     * @param req 查询流程定义的请求对象，包含分页信息和查询条件
     * @return 返回分页查询结果，包含流程定义列表和分页信息
     */
    @Override
    public Page<ProcessDefinition> queryProcessDefinitionWithPage(QueryProcessDefinitionReq req) {
        // 初始化分页工具以及参数，默认为第一页，每页10条数据
        long current = 1;
        long size = 10;

        // 检查 req 和 req.getPageReq() 是否为空，并设置分页参数
        if (req != null && req.getPageReq() != null) {
            current = req.getPageReq().getCurrent() > 0 ? req.getPageReq().getCurrent() : current;
            size = req.getPageReq().getSize() > 0 ? req.getPageReq().getSize() : size;
        }

        // 初始化分页对象
        Page<ProcessDefinition> page = new Page<>(current, size);

        try {
            // 调用mapper分页查询
            return this.baseMapper.queryProcessDefinitionWithPage(page, req);
        } catch (Exception e) {
            // 捕获异常并记录日志，避免系统崩溃
            log.error("查询流程定义分页失败，请求参数: {}", req, e);
            throw new BusinessException("查询流程定义分页失败，请稍后重试", e);
        }
    }

    /**
     * 获取流程定义的XML
     * <p>
     * 此方法用于根据流程定义的键查询最新的流程定义，并返回其对应的XML
     * 如果输入参数为空或流程定义不存在，则返回空字符串
     *
     * @param queryOneProcessDefinitionReq 查询流程定义的请求对象，包含流程定义的键
     * @return 返回流程定义的XML字符串，如果查询失败或无结果，则返回空字符串
     */
    @Override
    public String getProcessDefinitionXml(QueryOneProcessDefinitionReq queryOneProcessDefinitionReq) {
        // 初始化 xml
        String xml = "";

        // 参数判空
        if (queryOneProcessDefinitionReq == null || StringUtils.isBlank(queryOneProcessDefinitionReq.getProcessKey())) {
            log.warn("Invalid input: QueryOneProcessDefinitionReq is null or processKey is blank.");
            return xml;
        }

        try {
            // 查询最新版本的流程定义信息
            ProcessDefinition processDefinition = lambdaQuery()
                    .eq(ProcessDefinition::getProcessKey, queryOneProcessDefinitionReq.getProcessKey())
                    .orderByDesc(ProcessDefinition::getProcessVersion)
                    .last("limit 1")
                    .one();

            // 结果判空
            if (processDefinition == null || StringUtils.isBlank(processDefinition.getXmlMongoId())) {
                log.warn("No valid process definition found for processKey: {}",
                        queryOneProcessDefinitionReq.getProcessKey());
                return xml;
            }

            // 查询流程定义的 XML 数据
            Optional<BpmnDefinitions> bpmnDefinitions =
                    bpmnDefinitionRepository.findById(processDefinition.getXmlMongoId());

            // 结果判空
            if (bpmnDefinitions.isPresent()) {
                xml = bpmnDefinitions.get().getBpmnXml();
            } else {
                log.warn("No BpmnDefinitions found for xmlMongoId: {}", processDefinition.getXmlMongoId());
            }
        } catch (Exception e) {
            log.error("Error occurred while querying process definition or bpmn definitions.", e);
        }

        return xml;
    }

    /**
     * 保存流程定义的 BPMN XML 数据。
     *
     * @param saveProcessDefinitionXmlReq 请求对象，包含流程定义的键和需要保存的 XML 数据。
     *                                     - processKey: 流程定义的唯一标识键。
     *                                     - bpmnXml: 需要保存的 BPMN XML 数据。
     * @return Boolean 返回保存操作的结果，成功返回 true，失败抛出异常。
     * @throws BusinessException 如果流程定义不存在或保存过程中出现业务异常，则抛出此异常。
     */
    @Override
    public Boolean saveBpmnXml(SaveProcessDefinitionXmlReq saveProcessDefinitionXmlReq) {
        // 初始化 BpmnDefinitions 对象，用于存储新的 BPMN XML 数据
        BpmnDefinitions savedBpmnDefinitions = new BpmnDefinitions();

        // 校验请求参数是否合法
        validateRequest(saveProcessDefinitionXmlReq);

        try {
            // 查询最新版本的流程定义信息，按流程定义键和版本号降序排序，取第一条记录
            ProcessDefinition processDefinition = lambdaQuery()
                    .eq(ProcessDefinition::getProcessKey, saveProcessDefinitionXmlReq.getProcessKey())
                    .orderByDesc(ProcessDefinition::getProcessVersion)
                    .last("limit 1")
                    .one();

            // 如果未找到对应的流程定义，记录错误日志并抛出业务异常
            if (processDefinition == null) {
                log.error("流程定义已不存在");
                throw new BusinessException("流程定义已不存在");
            }

            // 删除 MongoDB 中现有的 XML 数据，确保数据一致性
            deleteExistingXmlData(processDefinition.getXmlMongoId());

            // 创建新的 BpmnDefinitions 对象，并设置流程定义的 XML 数据
            BpmnDefinitions bpmnDefinitions = createBpmnDefinitions(processDefinition, saveProcessDefinitionXmlReq);

            // 将新的 BpmnDefinitions 对象保存到 MongoDB 中
            savedBpmnDefinitions = bpmnDefinitionRepository.save(bpmnDefinitions);

            // 更新流程定义中的 mongodbId 字段，确保 MySQL 和 MongoDB 数据的一致性
            processDefinition.setXmlMongoId(savedBpmnDefinitions.getId());
            updateById(processDefinition);

            return true;
        } catch (BusinessException e) {
            // 捕获业务异常，记录错误日志并重新抛出
            log.error("保存流程定义失败，出现业务异常，具体异常原因: {}", e.getMessage());
            throw e;
        } catch (Exception e) {
            // 捕获未知异常，记录错误日志，并删除 MongoDB 中新增的数据以保证事务一致性
            log.error("保存流程定义失败，出现未知业务异常，具体异常原因: {}", e.getMessage());

            if (savedBpmnDefinitions.getId() != null) {
                bpmnDefinitionRepository.deleteById(savedBpmnDefinitions.getId());
            }

            // 抛出新的业务异常，包含原始异常信息
            throw new BusinessException("保存流程定义失败，出现未知业务异常", e);
        }
    }

    /**
     * 部署 BPMN XML 流程定义。
     *
     * @param deployProcessDefinitionXmlReq 包含部署流程定义所需信息的请求对象。
     *                                      必须包含有效的流程定义标识和相关信息。
     * @return 返回布尔值，表示部署是否成功。如果方法执行完成且未抛出异常，则返回 true。
     * @throws BusinessException 如果在部署过程中发生业务异常，则抛出此异常。
     *                           异常信息会记录日志并重新抛出。
     * @throws Exception         如果在部署过程中发生未知异常，则包装为 BusinessException 抛出。
     */
    @Override
    public Boolean deployBpmnXml(DeployProcessDefinitionXmlReq deployProcessDefinitionXmlReq) {
        try {
            // 校验请求参数的合法性，确保输入数据符合业务要求
            validateRequest(deployProcessDefinitionXmlReq);

            // 查询流程定义信息，获取与请求参数匹配的流程定义对象
            ProcessDefinition processDefinition = fetchProcessDefinition(deployProcessDefinitionXmlReq);

            // 获取流程定义对应的 BPMN XML 数据，用于后续部署操作
            String bpmnXml = fetchBpmnXml(processDefinition);

            // 使用 Flowable 的 RepositoryService 部署流程定义
            repositoryService.createDeployment()
                    .addString(processDefinition.getProcessKey() + Flowable7Constants.BPMN_XML_SUFFIX, bpmnXml)
                    .name(processDefinition.getProcessName())
                    .key(processDefinition.getProcessKey())
                    .deploy();

            // 将流程定义的状态变更为 1：发布
            processDefinition.setProcessStatus(ProcessStatus.PUBLISH.getCode());
            updateById(processDefinition);
        } catch (BusinessException e) {
            // 记录业务异常日志，并重新抛出异常以便调用方处理
            log.error("部署流程时出现业务异常，具体异常原因: {}", e.getMessage(), e);
            throw e;
        } catch (Exception e) {
            // 记录未知异常日志，并将异常包装为 BusinessException 抛出
            log.error("部署流程时出现未知异常，具体异常原因: {}", e.getMessage(), e);
            throw new BusinessException("部署流程时出现未知异常", e);
        }

        return true;
    }

    // 参数校验方法
    private void validateRequest(SaveProcessDefinitionXmlReq request) {
        if (request == null
                || StringUtils.isBlank(request.getProcessKey())
                || StringUtils.isBlank(request.getProcessXml())) {
            log.error("参数校验失败，请重新选择流程定义");
            throw new BusinessException("参数校验失败，请重新选择流程定义");
        }
    }

    // 删除现有 XML 数据方法
    private void deleteExistingXmlData(String xmlMongoId) {
        if (StringUtils.isNotBlank(xmlMongoId)) {
            bpmnDefinitionRepository.deleteById(xmlMongoId);
        }
    }

    // 创建 BpmnDefinitions 对象方法
    private BpmnDefinitions createBpmnDefinitions(ProcessDefinition processDefinition,
                                                  SaveProcessDefinitionXmlReq request) {
        BpmnDefinitions bpmnDefinitions = new BpmnDefinitions();
        bpmnDefinitions.setProcessKey(processDefinition.getProcessKey());
        bpmnDefinitions.setProcessName(processDefinition.getProcessName());
        bpmnDefinitions.setProcessVersion(processDefinition.getProcessVersion());
        bpmnDefinitions.setProcessStatus(processDefinition.getProcessStatus());
        bpmnDefinitions.setBpmnXml(request.getProcessXml());
        return bpmnDefinitions;
    }

    // 参数校验方法
    private void validateRequest(DeployProcessDefinitionXmlReq deployProcessDefinitionXmlReq) {
        if (deployProcessDefinitionXmlReq == null
                || StringUtils.isBlank(deployProcessDefinitionXmlReq.getProcessKey())
                || deployProcessDefinitionXmlReq.getProcessVersion() == null) {
            log.error("部署流程时参数校验失败，请重新选择流程定义");
            throw new BusinessException("部署流程时参数校验失败，请重新选择流程定义");
        }
    }

    // 查询流程定义信息方法
    private ProcessDefinition fetchProcessDefinition(DeployProcessDefinitionXmlReq deployProcessDefinitionXmlReq) {
        ProcessDefinition processDefinition = lambdaQuery()
                .eq(ProcessDefinition::getProcessKey, deployProcessDefinitionXmlReq.getProcessKey())
                .eq(ProcessDefinition::getProcessVersion, deployProcessDefinitionXmlReq.getProcessVersion())
                .one();
        if (processDefinition == null) {
            log.error("部署流程时流程定义已不存在");
            throw new BusinessException("部署流程时流程定义已不存在");
        }
        return processDefinition;
    }

    // 获取 BPMN XML 数据方法
    private String fetchBpmnXml(ProcessDefinition processDefinition) {
        Optional<BpmnDefinitions> bpmnDefinitions = bpmnDefinitionRepository.findById(processDefinition.getXmlMongoId());
        return bpmnDefinitions.map(def -> {
            String bpmnXml = def.getBpmnXml();

            // 20250426 修改 增加翻译 camunda-xml 到 flowable7-xml 的逻辑（先直接用字符串替换的方法）
            // 这里是因为前端集成的bpmn-js是Camunda提供的一个流程设计器工具，flowable无法发起流程
            bpmnXml = bpmnXml.replaceAll("xmlns:camunda=\"http://camunda.org/schema/1.0/bpmn\"","xmlns:flowable=\"http://flowable.org/bpmn\" ");
            bpmnXml = bpmnXml.replaceAll("targetNamespace=\"http://bpmn.io/bpmn\"","targetNamespace=\"http://www.flowable.org/processdef\"");
            bpmnXml = bpmnXml.replaceAll("exporterVersion=\"5.1.2\"","exporterVersion=\"7.1.0\"");
            bpmnXml = bpmnXml.replaceAll("camunda:","flowable:");

            if (StringUtils.isBlank(bpmnXml)) {
                log.error("部署流程时流程定义的 BPMN XML 数据为空");
                throw new BusinessException("部署流程时流程定义的 BPMN XML 数据为空");
            }
            return bpmnXml;
        }).orElseThrow(() -> {
            log.error("部署流程时流程定义的 BPMN XML 数据为空");
            return new BusinessException("部署流程时流程定义的 BPMN XML 数据为空");
        });
    }

}
